package net.danielseidl.ludumdare.ld48.level;

import java.util.ArrayList;
import java.util.logging.Logger;

import net.danielseidl.ludumdare.ld48.character.Monster;
import net.danielseidl.ludumdare.ld48.character.SpeedType;
import net.danielseidl.ludumdare.ld48.tile.Tile;
import net.danielseidl.ludumdare.ld48.tile.TileException;
import net.danielseidl.ludumdare.ld48.tile.TileLoader;

/**
 * Used for storing and loading level data.
 *
 * @author    Daniel Seidl - info@daniel-seidl.net
 */
public class Level
	{
	private Logger LOGGER = Logger.getLogger(Level.class.getName());
	
	private TileLoader loader;
	
	private int stage;
	
	private int width;
	private int height;
	
	private String[] tileSets;
	
	private Tile[][] tilesLayerBelow;
	private Tile[][] tilesLayerLevel;
	
	private Tile defaultTile;
	
	private int[] startLocation;
	private int[] exitLocation;
	
	private ArrayList<Monster> monsters = new ArrayList<Monster>();
	
	/**
	 * Creates a new level based on the given data.
	 * @param loader The tile loader to use.
	 * @param stage The level's stage.
	 * @param width The level's width.
	 * @param height The level's height.
	 */
	public Level(TileLoader loader, int stage, int width, int height)
		{
		this.loader = loader;
		this.stage = stage;
		this.width = width;
		this.height = height;
		
		// Set level start and exit location.
		this.setStartAndExitLocation();
		
		// Create the tile layers.
		this.tilesLayerBelow = new Tile[this.width][this.height];
		this.tilesLayerLevel = new Tile[this.width][this.height];
		this.loadUsedTileSets();
		this.createLayers();
		
		// Create monsters.
		this.createMonsters();
		
		this.LOGGER.info("initialized level stage " + this.stage);
		}
	
	/**
	 * Create monsters for this level.
	 */
	private void createMonsters()
		{
		this.monsters.add(new Monster(new int[]{1, 1, 15, 15}, this, SpeedType.SLOW, "monster01"));
		this.monsters.add(new Monster(new int[]{3, 3, 15, 15}, this, SpeedType.MEDIUM, "monster02"));
		}
	
	/**
	 * Sets the start and the exit location for this level.
	 */
	private void setStartAndExitLocation()
		{
		switch (this.stage % 4)
			{
			case 0:
				this.startLocation = new int[]{1, 1, 15, 0};
				this.exitLocation = new int[]{this.width - 2, this.height - 2};
				break;
			case 1:
				this.startLocation = new int[]{this.width - 2, 1, 15, 0};
				this.exitLocation = new int[]{1, this.height - 2};				
				break;
			case 2:
				this.startLocation = new int[]{1, this.height - 2, 15, 0};
				this.exitLocation = new int[]{this.width - 2, 1};		
				break;
			case 3:
				this.startLocation = new int[]{this.width - 2, this.height - 2, 15, 0};
				this.exitLocation = new int[]{1, 1};					
				break;
			}
		}
	
	/**
	 * Loads the used tile sets in this level.
	 */
	private void loadUsedTileSets()
		{
		// TODO -> Tile sets should be based on the generation process!
		this.tileSets = new String[]{"default"};
		this.loader.loadTileSets(this.tileSets);
		}
	
	/**
	 * Creates the layers.
	 */
	private void createLayers()
		{
		try
			{
			this.defaultTile = this.loader.getTileFromStorage("default#default");
			this.createTilesLayerBelow();
			this.createTilesLayerLevel();
			}
		catch (TileException e)
			{
			this.LOGGER.severe(e.getMessage());
			this.LOGGER.severe("terminating running system...");
			System.exit(1);		
			}
		this.LOGGER.info("created tile layers for level");
		}
	
	/**
	 * Creates the tiles below the player.
	 * @throws TileException If a needed tile can not be loaded.
	 */
	private void createTilesLayerBelow() throws TileException
		{
		// TODO -> just for testing, should be based on a tile set!
		Tile def = this.loader.getTileFromStorage("default#main");
		Tile exit = this.loader.getTileFromStorage("default#exit");
		
		// TODO -> just for testing, should be nicely generated
		for (int i = 0; i < this.height; i++)
			{
			for (int j = 0; j < this.width; j++)
				{
				this.tilesLayerBelow[j][i] = def;
				}
			}
		
		this.tilesLayerBelow[this.exitLocation[0]][this.exitLocation[1]] = exit;
		}
	
	/**
	 * Creates the tiles in the same layer as the player.
	 * @throws TileException If a needed tile can not be loaded.
	 */
	private void createTilesLayerLevel() throws TileException
		{
		// TODO -> just for testing, should be based on a tile set!
		Tile wall = this.loader.getTileFromStorage("default#wall");
		
		this.tilesLayerLevel[3][3] = wall;
		}
	
	/**
	 * Returns the used tile at a given location in the layer below the player.
	 * @param x The x coordinate of the tile.
	 * @param y The y coordinate of the tile.
	 * @return The tile used at the given location or the default tile if there's no tile.
	 */
	public Tile getTileFromLayerBelow(int x, int y)
		{
		if (x >= 0 && y >= 0 && x < this.width && y < this.height)
			{
			return this.tilesLayerBelow[x][y];
			}
		
		return this.defaultTile;
		}
	
	/**
	 * Returns the used tile at a given location in the layer same as the player.
	 * @param x The x coordinate of the tile.
	 * @param y The y coordinate of the tile.
	 * @return The tile used at the given location or null if there's no tile.
	 */
	public Tile getTileFromLayerLevel(int x, int y)
		{
		if (x >= 0 && y >= 0 && x < this.width && y < this.height)
			{
			return this.tilesLayerLevel[x][y];
			}
		
		return null;
		}
	
	/**
	 * Returns the level's width.
	 * @return The level's width.
	 */
	public int getLevelWidth()
		{
		return this.width;
		}
	
	/**
	 * Returns the level's height.
	 * @return The level's height.
	 */
	public int getLevelHeight()
		{
		return this.width;
		}
	
	/**
	 * Returns the level's start location.
	 * @return The level's start location.
	 */
	public int[] getLevelStartLocation()
		{
		return this.startLocation;
		}
	
	/**
	 * Returns the level's exit location.
	 * @return The level's exit location.
	 */
	public int[] getLevelExitLocation()
		{
		return this.exitLocation;
		}
	
	/**
	 * Returns the level's stage.
	 * @return The level's stage.
	 */
	public int getLevelStage()
		{
		return this.stage;
		}
	
	/**
	 * Returns the list of monsters.
	 * @return The list of monsters.
	 */
	public ArrayList<Monster> getMonsters()
		{
		return this.monsters;
		}
	}